/*
 * anima/pc/firmware.cc - Firmware functions for PC platform.
 * Copyright (C) 2007  Mikhail Vorozhtsov
 * See the LICENSE section of the README file for details.
 */

/* $Id$ */

#include HEADER (anima,firmware.hh)
#include HEADER (anima,pdb.hh)
#include HEADER (anima,mm.hh)
#include HEADER (anima,init.hh)
#include HEADER (anima,console.hh)
#include HEADER (elbp/pc,elbp.hh)
#include HEADER (utils,memory.hh)

START_NAMESPACE2 (anima,pc)

static const elbp::pc::mem_region_info_t *mem_region_info;

END_NAMESPACE2

START_NAMESPACE (anima)

using pc::mem_region_info;

bool
firmware::init (const elbp::info_t *elbp_info)
{
  const elbp::pc::info_t *pc_info
    = (const elbp::pc::info_t *) elbp_info->platform_info ();

  if ((pc_info->feats & elbp::pc::info_t::FEAT_MMAP) == 0)
    {
      console::error ("Boot loader did not provide Memory Map.");
      return false;
    }

  mem_region_info = pc_info->mem_region_info;

  return ANIMA_ARCH::firmware::init (elbp_info);
}

bool
firmware::page_used_p (u64_t addr)
{
  if (init::get_main_space ().null_p ())
    for (const elbp::pc::mem_region_info_t *info = mem_region_info; info;)
      {
        if (utils::in_range_p (addr, (u64_t) info,
                               (u64_t) info
                               + sizeof (elbp::pc::mem_region_info_t),
                               mm::PAGE_SIZE))
          return true;

        if (mm::initialized_p ())
          {
            elbp::pc::mem_region_info_t info_copy;

            if (!mm::cp ((u64_t) info, &info_copy, sizeof (info_copy)))
              return true;

            info = info_copy.next;
          }
        else
          info = info->next;
      }

  return false;
}

bool
firmware::remap ()
{
  // Map VGA video memory.
  if (console::vga_console_p ())
    {
      void *video_mem = mm::map ((u64_t) console::get_vga_video_mem (),
                                 (mm::tte_t::flag_t)
                                   (mm::tte_t::FLAG_IO
                                    | mm::tte_t::FLAG_WRITE));

      if (!video_mem)
        return false;

      console::set_vga_video_mem (video_mem);
    }

  return true;
}

const char *
firmware::region_t::type_string (type_t type)
{
  switch (type)
    {
    case TYPE_AVAILABLE:
      return "Available";
      break;
    case TYPE_RESERVED:
      return "Reserved";
      break;
    case TYPE_ACPI_RECLAIM:
      return "ACPI";
      break;
    case TYPE_ACPI_NVS:
      return "ACPI NVS";
      break;
    case TYPE_UNUSABLE:
      return "Unusable";
      break;
    default:
      return "Unknown";
    }
}

const void *
firmware::region_t::next (const void *prev, region_t &region)
{
  const elbp::pc::mem_region_info_t *info;
  elbp::pc::mem_region_info_t info_copy;
  u64_t info_paddr;

  if (prev)
    {
      if (mm::initialized_p ())
        {
          if (!mm::cp ((u64_t) prev, &info_copy, sizeof (info_copy)))
            return NULL;
          info = &info_copy;
        }
      else
        info = (const elbp::pc::mem_region_info_t *) prev;

      info_paddr = (u64_t) info->next;
    }
  else
    info_paddr = (u64_t) mem_region_info;

  if (info_paddr == 0)
    return NULL;

  if (mm::initialized_p ())
    {
      if (!mm::cp (info_paddr, &info_copy, sizeof (info_copy)))
        return NULL;
      info = &info_copy;
    }
  else
    info = (const elbp::pc::mem_region_info_t *) info_paddr;

  region.addr = info->addr;
  region.size = info->size;
  region.type = (type_t) info->type;

  return (const void *) info_paddr;
}

bool
firmware::region_t::available_p () const
{
  return type == TYPE_AVAILABLE;
}

END_NAMESPACE

